#include ".\dxMeshAnimation.h"
#include ".\dxUtility.h"
#include ".\dxMeshHierarchy.h"
using namespace std;

const float kMoveTransitionTime=0.25f;

dxMeshAnimation::dxMeshAnimation(LPDIRECT3DDEVICE9 d3dDevice)
{
	m_d3dDevice = d3dDevice;
	m_speedAdjust = 1.0f;
	m_firstMesh = 0;
	m_currentTrack = 0;
	m_currentTime = 0;
	m_numAnimationSets = 0;
	m_currentAnimationSet = 0;
	m_maxBones = 0;
	m_sphereRadius = 0;
	m_sphereCentre = D3DXVECTOR3(0,0,0);
	m_boneMatrices = 0;
}

dxMeshAnimation::~dxMeshAnimation(void)
{
	if (m_animController)
	{
		m_animController->Release();
		m_animController=0;
	}

	if (m_frameRoot)
	{
		dxMeshHierarchy memoryAllocator;
		D3DXFrameDestroy(m_frameRoot, &memoryAllocator);
		m_frameRoot=0;
	}

	if (m_boneMatrices)
	{
		delete []m_boneMatrices;
		m_boneMatrices=0;
	}
}

bool dxMeshAnimation::Load(const std::string &filename)
{
	dxMeshHierarchy *memoryAllocator=new dxMeshHierarchy;

	std::string currentDirectory=CUtility::GetTheCurrentDirectory();

	std::string xfilePath;
	CUtility::SplitPath(filename,&xfilePath,&m_filename);

	SetCurrentDirectory(xfilePath.c_str());

	HRESULT hr = D3DXLoadMeshHierarchyFromX(filename.c_str(), 
							D3DXMESH_MANAGED, 
							m_d3dDevice, 
							memoryAllocator, 
							NULL, 
							&m_frameRoot, 
							&m_animController);

	delete memoryAllocator;
	memoryAllocator=0;

	SetCurrentDirectory(currentDirectory.c_str());
	
	if (CUtility::FailedHr(hr))
		return false; 

	// if the x file contains any animation remember how many sets there are
	if(m_animController)
		m_numAnimationSets = m_animController->GetMaxNumAnimationSets();

	// Bones for skining
	if(m_frameRoot)
	{
		// Set the bones up
		SetupBoneMatrices((D3DXFRAME_EXTENDED*)m_frameRoot, NULL);

		// Create the bone matrices array for use during FrameMove to hold the final transform
		m_boneMatrices  = new D3DXMATRIX[m_maxBones];
		ZeroMemory(m_boneMatrices, sizeof(D3DXMATRIX)*m_maxBones);

		// Calculate the Bounding Sphere for this model (used in CalculateInitialViewMatrix to position camera correctly)
		D3DXFrameCalculateBoundingSphere(m_frameRoot, &m_sphereCentre, &m_sphereRadius);
	}
	
	return true;
}

D3DXVECTOR3 dxMeshAnimation::GetInitialCameraPosition() const
{
	D3DXVECTOR3 cameraPos(0.0f,m_sphereCentre.y,-(m_sphereRadius*3));
	//D3DXVECTOR3 cameraPos(10.0f,20.0f,20.0f);
	return cameraPos;
}

void dxMeshAnimation::SetupBoneMatrices(D3DXFRAME_EXTENDED *pFrame, LPD3DXMATRIX pParentMatrix)
{
	// Cast to our extended structure first
	D3DXMESHCONTAINER_EXTENDED* pMesh = (D3DXMESHCONTAINER_EXTENDED*)pFrame->pMeshContainer;

	// If this frame has a mesh
	if(pMesh)
	{
		// We need to remember which is the first mesh in the hierarchy for later when we 
		// update (FrameMove)
		if(!m_firstMesh)
			m_firstMesh = pMesh;
		
		// if there is skin info, then setup the bone matrices
		if(pMesh->pSkinInfo && pMesh->MeshData.pMesh)
		{
			// Create a copy of the mesh to skin into later
			D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE];
			if (FAILED(pMesh->MeshData.pMesh->GetDeclaration(Declaration)))
				return;

			pMesh->MeshData.pMesh->CloneMesh(D3DXMESH_MANAGED, 
				Declaration, m_d3dDevice, 
				&pMesh->exSkinMesh);

			// Max bones is calculated for later use (to know how big to make the bone matrices array)
			m_maxBones=max(m_maxBones,(int)pMesh->pSkinInfo->GetNumBones());

			// For each bone work out its matrix
			for (unsigned int i = 0; i < pMesh->pSkinInfo->GetNumBones(); i++)
			{   
				// Find the frame containing the bone
				D3DXFRAME_EXTENDED* pTempFrame = (D3DXFRAME_EXTENDED*)D3DXFrameFind(m_frameRoot, 
						pMesh->pSkinInfo->GetBoneName(i));

				// set the bone part - point it at the transformation matrix
				pMesh->exFrameCombinedMatrixPointer[i] = &pTempFrame->exCombinedTransformationMatrix;
			}

		}
	}

	// Pass on to sibblings
	if(pFrame->pFrameSibling)
		SetupBoneMatrices((D3DXFRAME_EXTENDED*)pFrame->pFrameSibling, pParentMatrix);

	// Pass on to children
	if(pFrame->pFrameFirstChild)
		SetupBoneMatrices((D3DXFRAME_EXTENDED*)pFrame->pFrameFirstChild, &pFrame->exCombinedTransformationMatrix);
}

void dxMeshAnimation::FrameMove(float elapsedTime,const D3DXMATRIX *matWorld)
{
	// Adjust animation speed
	elapsedTime/=m_speedAdjust;

	// Advance the time and set in the controller
    if (m_animController != NULL)
        m_animController->AdvanceTime(elapsedTime, NULL);
        
    CUtility::DebugString("Debug frame: "+ToString(elapsedTime)+"\n");

	m_currentTime+=elapsedTime;

	// Now update the model matrices in the hierarchy
    UpdateFrameMatrices(m_frameRoot, matWorld);

	// If the model contains a skinned mesh update the vertices
	D3DXMESHCONTAINER_EXTENDED* pMesh = m_firstMesh;
	if(pMesh && pMesh->pSkinInfo)
	{
		unsigned int Bones = pMesh->pSkinInfo->GetNumBones();

		for (unsigned int i = 0; i < Bones; ++i)
			D3DXMatrixMultiply(&m_boneMatrices[i],&pMesh->exBoneOffsets[i], pMesh->exFrameCombinedMatrixPointer[i]);

		void *srcPtr=0;
		pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&srcPtr);

		void *destPtr=0;
		pMesh->exSkinMesh->LockVertexBuffer(0, (void**)&destPtr);

		// Update the skinned mesh 
		pMesh->pSkinInfo->UpdateSkinnedMesh(m_boneMatrices, NULL, srcPtr, destPtr);

		// Unlock the meshes vertex buffers
		pMesh->exSkinMesh->UnlockVertexBuffer();
		pMesh->MeshData.pMesh->UnlockVertexBuffer();
	}
}

void dxMeshAnimation::UpdateFrameMatrices(const D3DXFRAME *frameBase, const D3DXMATRIX *parentMatrix)
{
    D3DXFRAME_EXTENDED *currentFrame = (D3DXFRAME_EXTENDED*)frameBase;

	// If parent matrix exists multiply our frame matrix by it
    if (parentMatrix != NULL)
        D3DXMatrixMultiply(&currentFrame->exCombinedTransformationMatrix, &currentFrame->TransformationMatrix, parentMatrix);
    else
        currentFrame->exCombinedTransformationMatrix = currentFrame->TransformationMatrix;

	// If we have a sibling recurse 
    if (currentFrame->pFrameSibling != NULL)
        UpdateFrameMatrices(currentFrame->pFrameSibling, parentMatrix);

	// If we have a child recurse 
    if (currentFrame->pFrameFirstChild != NULL)
        UpdateFrameMatrices(currentFrame->pFrameFirstChild, &currentFrame->exCombinedTransformationMatrix);
}

void dxMeshAnimation::Render() const
{
	if (m_frameRoot)
		DrawFrame(m_frameRoot);
}

void dxMeshAnimation::DrawFrame(LPD3DXFRAME frame) const
{
	// Draw all mesh containers in this frame
    LPD3DXMESHCONTAINER meshContainer = frame->pMeshContainer;
    while (meshContainer)
    {
        DrawMeshContainer(meshContainer, frame);
        meshContainer = meshContainer->pNextMeshContainer;
    }

	// Recurse for sibblings
    if (frame->pFrameSibling != NULL)
        DrawFrame(frame->pFrameSibling);

    // Recurse for children
	if (frame->pFrameFirstChild != NULL)
        DrawFrame(frame->pFrameFirstChild);
}

void dxMeshAnimation::DrawMeshContainer(LPD3DXMESHCONTAINER meshContainerBase, LPD3DXFRAME frameBase) const
{
	// Cast to our extended frame type
	D3DXFRAME_EXTENDED *frame = (D3DXFRAME_EXTENDED*)frameBase;		

	// Cast to our extended mesh container
	D3DXMESHCONTAINER_EXTENDED *meshContainer = (D3DXMESHCONTAINER_EXTENDED*)meshContainerBase;
	
	if(((*(_D3DXMESHCONTAINER*)(&*meshContainer))).Name == (LPSTR)0x03e85c98)
	{
		//(*(_D3DMATRIX*)(&(*frame).exCombinedTransformationMatrix))
		D3DXMATRIX rotationMatrix;
		static float rotY = 0.5f;
		D3DXMatrixRotationY( &(*(D3DXMATRIX*)(&(*frame).exCombinedTransformationMatrix)), rotY );
		m_d3dDevice->SetTransform( D3DTS_VIEW, &rotationMatrix );
		rotY +=0.5f;
	}
	
	// Set the world transform
    m_d3dDevice->SetTransform(D3DTS_WORLD, &frame->exCombinedTransformationMatrix);

	// Loop through all the materials in the mesh rendering each subset
    for (unsigned int iMaterial = 0; iMaterial < meshContainer->NumMaterials; iMaterial++)
    {
		// use the material in our extended data rather than the one in meshContainer->pMaterials[iMaterial].MatD3D
		m_d3dDevice->SetMaterial( &meshContainer->exMaterials[iMaterial] );
		m_d3dDevice->SetTexture( 0, meshContainer->exTextures[iMaterial] );

		// Select the mesh to draw, if there is skin then use the skinned mesh else the normal one
		LPD3DXMESH pDrawMesh = (meshContainer->pSkinInfo) ? meshContainer->exSkinMesh: meshContainer->MeshData.pMesh;

		// Finally Call the mesh draw function
        pDrawMesh->DrawSubset(iMaterial);
    }
}

void dxMeshAnimation::processMeshData()
{
	//LPSTR frameName =  m_frameRoot->pFrameFirstChild->pFrameSibling->Name;
	LPSTR meshName =  m_frameRoot->pFrameFirstChild->pFrameSibling->pFrameSibling->pFrameSibling->pFrameSibling->pFrameSibling->pMeshContainer->Name;
	//CUtility::DebugString("Debug frame: "+ToString(frameName)+"\n");
	CUtility::DebugString("Debug frame: "+ToString(meshName)+"\n");
	
	//CUtility::DebugString("Num faces: "+ToString(m_frameRoot->pMeshContainer->MeshData.pMesh->GetNumFaces())+"\n");
	//CUtility::DebugString("Num vertices: "+ToString(m_frameRoot->pMeshContainer->MeshData.pMesh->GetNumVertices())+"\n");
	
	//LPD3DXMESH pMesh;
	//D3DXMESHCONTAINER_EXTENDED* pMesh;
	LPDIRECT3DVERTEXBUFFER9 pVB;
    LPDIRECT3DINDEXBUFFER9 pIB;
	//m_frameRoot->TransformationMatrix._14 = 4.5f;
	//m_frameRoot->pMeshContainer->MeshData.pMesh->CloneMesh(D3DXMESH_MANAGED, m_frameRoot->pMeshContainer->MeshData.pMesh->GetFVF(), m_d3dDevice, &pMesh );
		
	WORD* pIndices;
	D3DVERTEX* pVertices;
				
	m_frameRoot->pMeshContainer->MeshData.pMesh->GetVertexBuffer( &pVB );
    m_frameRoot->pMeshContainer->MeshData.pMesh->GetIndexBuffer( &pIB );
    
    pIB->Lock( 0, 20, ( void** )&pIndices, 0 );
    pVB->Lock( 0, 20, ( void** )&pVertices, 0 );
    
    //TO DO..
    
    pVB->Unlock();
    pIB->Unlock();

    SAFE_RELEASE( pVB );
    SAFE_RELEASE( pIB );
}

void dxMeshAnimation::SetAnimationSet(unsigned int index)
{
	//string nameMesh = m_frameRoot->Name;
	
	if(m_animController)
	{
		//m_numAnimationSets = m_animController->GetMaxNumAnimationSets();
		if (index==m_currentAnimationSet)
			return;

		if (index>=m_numAnimationSets)
			index=0;

		// Remember current animation
		m_currentAnimationSet=index;

		// Get the animation set from the controller
		LPD3DXANIMATIONSET set;
		m_animController->GetAnimationSet(m_currentAnimationSet, &set );	

		// Alternate tracks
		DWORD newTrack = ( m_currentTrack == 0 ? 1 : 0 );

		// Assign to our track
		m_animController->SetTrackAnimationSet( newTrack, set );
		
		//if(m_numAnimationSets == 0)
			set->Release();	

		// Clear any track events currently assigned to our two tracks
		m_animController->UnkeyAllTrackEvents( m_currentTrack );
		m_animController->UnkeyAllTrackEvents( newTrack );

		// Add an event key to disable the currently playing track kMoveTransitionTime seconds in the future
		m_animController->KeyTrackEnable( m_currentTrack, FALSE, m_currentTime + kMoveTransitionTime );
		// Add an event key to change the speed right away so the animation completes in kMoveTransitionTime seconds
		m_animController->KeyTrackSpeed( m_currentTrack, 0.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
		// Add an event to change the weighting of the current track (the effect it has blended with the secon track)
		m_animController->KeyTrackWeight( m_currentTrack, 0.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );

		// Enable the new track
		m_animController->SetTrackEnable( newTrack, TRUE );
		// Add an event key to set the speed of the track
		m_animController->KeyTrackSpeed( newTrack, 1.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );
		// Add an event to change the weighting of the current track (the effect it has blended with the first track)
		// As you can see this will go from 0 effect to total effect(1.0f) in kMoveTransitionTime seconds and the first track goes from 
		// total to 0.0f in the same time.
		m_animController->KeyTrackWeight( newTrack, 1.0f, m_currentTime, kMoveTransitionTime, D3DXTRANSITION_LINEAR );

		// Remember current track
		m_currentTrack = newTrack;
    }
}

void dxMeshAnimation::NextAnimation()
{	
	unsigned int newAnimationSet=m_currentAnimationSet+1;
	if (newAnimationSet>=m_numAnimationSets)
		newAnimationSet=0;
		

	SetAnimationSet(newAnimationSet);
}

std::string dxMeshAnimation::GetAnimationSetName(unsigned int index)
{
	if (index>=m_numAnimationSets)
		return "Error: No set exists";

	// Get the animation set
	LPD3DXANIMATIONSET set;
	m_animController->GetAnimationSet(m_currentAnimationSet, &set );

	std::string nameString(set->GetName());

	set->Release();

	return nameString;
}

void dxMeshAnimation::AnimateSlower()
{
	m_speedAdjust+=0.1f;
}

void dxMeshAnimation::AnimateFaster()
{
	if (m_speedAdjust>0.1f)
		m_speedAdjust-=0.1f;
}